package cn.maihe.elg.operation.centers.epoint.channel.service;

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.MD5;
import cn.maihe.elg.operation.centers.epoint.channel.EpointBaseChannelService;
import cn.maihe.elg.operation.centers.epoint.dto.EpointBaseRespDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.EpointBaohanFileCompleteNoticeReqDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.EpointDownloadPublickeyReqDTO;
import cn.maihe.elg.operation.centers.epoint.dto.channel.EpointUploadBaohanFileReqDTO;
import cn.maihe.elg.operation.centers.epoint.enums.EpointChannelMethod;
import cn.maihe.elg.operation.centers.epoint.enums.EpointRespCodeEnum;
import cn.maihe.elg.operation.centers.supports.model.bo.InnerUploadBaohanFileBo;
import cn.maihe.elg.operation.model.dto.InnerBaseResp;
import cn.maihe.elg.operation.model.enums.AttachmentFileType;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.repository.entity.AcceptOrderInfoDO;
import cn.maihe.elg.operation.repository.entity.AcceptOrderInfoDOCondition;
import cn.maihe.elg.operation.service.order.AcceptOrderInfoService;
import cn.maihe.elg.operation.service.order.GuaranteeAttachmentInfoService;
import cn.maihe.elg.operation.supports.system.ElgResourceService;
import cn.maihe.elg.operation.utils.SM4Util;
import cn.maihe.elg.operation.utils.ValidateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * @Description 金融服务系统作为服务方，金融机构作为请求方。
 * 为了规范电子保函业务，金融机构的保函业务需要满足以下几个要求：
 * 1. 金融机构开具的保函必须为电子件，不接受纸质文件或纸质文件的电子扫描件；
 * 2. 金融机构使用第三方CA机构颁发的数字证书进行电子签名（签章），电子保函相关文件必须遵循PKI体系的数字签名系统和数字证书技术标准和规范，具有明确的法律效力；
 * SM4对整个文件进行加密，加密后进行安全传输
 * @Author WJH
 * @Date 2021/04/21
 */
@Service
@Slf4j
public class NPEpointUploadBaohanFileChannelService extends EpointBaseChannelService<EpointUploadBaohanFileReqDTO, EpointBaseRespDTO> {

    @Resource
    private ElgResourceService elgResourceService;
    @Resource
    private GuaranteeAttachmentInfoService guaranteeAttachmentInfoService;
    @Resource
    private NPEpointDownloadPublickeyChannelService downloadPublickeyChannelService;
    @Resource
    private NPEpointBaohanFileCompleteNoticeChannelService baohanFileCompleteNoticeChannelService;
    @Resource
    private AcceptOrderInfoService acceptOrderInfoService;

    @Override
    public EpointChannelMethod methodEnum() {
        return EpointChannelMethod.uploadbaohanfile;
    }

    /**
     * 128位
     */
    public byte[] generateKey() throws Exception {
        return SM4Util.generateKey();
    }

    /**
     * 128位
     */
    public byte[] generateIV() throws Exception {
        return SM4Util.generateKey();
    }

    public String getSm4Key(CenterNoEnum centerNoEnum) {
        return this.epointCenterInfosConfig.getAuthConfig(centerNoEnum).getCenterInfo().getSm4Key();
    }

    public String getSm4Iv(CenterNoEnum centerNoEnum) {
        return this.epointCenterInfosConfig.getAuthConfig(centerNoEnum).getCenterInfo().getSm4Iv();
    }

    /**
     * base64编码后字符串
     */
    protected String downloadPublicKey(String acceptOrderNo, CenterNoEnum centerNoEnum, String applyNo) {
        EpointDownloadPublickeyReqDTO reqDTO = new EpointDownloadPublickeyReqDTO();
        reqDTO.setApplyno(applyNo);
        InnerBaseResp<String> innerBaseResp = downloadPublickeyChannelService.downloadPublickey(acceptOrderNo, centerNoEnum, reqDTO);
        if (innerBaseResp.getRespCode().isSuccess()) {
            return innerBaseResp.getData();
        }
        throw new RuntimeException(innerBaseResp.getMessage());
    }

    public InnerBaseResp uploadBaohanFile(String acceptOrderNo, CenterNoEnum centerNoEnum, InnerUploadBaohanFileBo bo) {
        InnerBaseResp<String> innerBaseResp = null;

        try {
            ValidateUtil.validate(bo);
        } catch (Exception e) {
            innerBaseResp = InnerBaseResp.error("保函文件上传参数有误:" + e.getMessage());
            return innerBaseResp;
        }

        String sm2PublicKey = null;
        try {
            sm2PublicKey = this.downloadPublicKey(acceptOrderNo, centerNoEnum, bo.getApplyno());
            log.info("{} << 下载sm2公钥值: {}", acceptOrderNo, sm2PublicKey);
        } catch (Exception e) {
            innerBaseResp = InnerBaseResp.error(e.getMessage());
            return innerBaseResp;
        }
        Assert.hasText(sm2PublicKey, () -> "下载sm2公钥内容为空");

        try {
            AttachmentFileType fileType;
//            if ("1".equals(bo.getFiletype())) {
//                fileType = AttachmentFileType.TYPE_16;
//            } else if ("2".equals(bo.getFiletype())) {
            fileType = AttachmentFileType.TYPE_0;
//            } else {
//                throw new RuntimeException("fileType[" + bo.getFiletype() + "]类型错误");
//            }

            Path guaranteePath = guaranteeAttachmentInfoService.getGuaAttPath(bo.getAcceptOrderNo(), fileType);
            if (guaranteePath == null || Files.notExists(guaranteePath)) {
                throw new RuntimeException("保函文件不存在");
            }
            String sm4Key = this.getSm4Key(centerNoEnum);
            String sm4Iv = this.getSm4Iv(centerNoEnum);
            log.debug("{} << sm4Key原值: {}", acceptOrderNo, sm4Key);
            log.debug("{} << sm4Iv原值: {}", acceptOrderNo, sm4Iv);
            byte[] sm4KeyByte = sm4Key.getBytes();
            byte[] sm4IVByte = sm4Iv.getBytes();

            SM2 sm2 = SmUtil.sm2(null, sm2PublicKey);
            sm2.setMode(SM2Engine.Mode.C1C2C3);

            /**sm4加密字符串*/
            byte[] fileBytes = Files.readAllBytes(guaranteePath);
            String fileB64Str = Base64.encodeBase64String(fileBytes);
            byte[] encryptFFileByte = SM4Util.encrypt_CBC_Padding(sm4KeyByte, sm4IVByte, fileB64Str.getBytes(StandardCharsets.UTF_8));

            //测试本地解密文件
//            {
//                byte[] encryptBySm4 = Base64.decodeBase64(fileEncryptBase64);
//                byte[] encryptByB64 = SM4Util.decrypt_CBC_Padding(sm4KeyByte, sm4IVByte, encryptBySm4);
//                byte[] decryptFileAy = Base64.decodeBase64(encryptByB64);
//                Path tempPath = guaranteePath.resolveSibling("sm4还原文件-" + elgResourceService.getFileNameAndType(guaranteePath));
//                if(Files.notExists(tempPath)){
//                    Files.createFile(tempPath);
//                }
//                Files.write(tempPath, decryptFileAy, StandardOpenOption.TRUNCATE_EXISTING);
//                log.error("sm4还原文件完成:{}", tempPath.normalize().toString());
//            }
            int length = encryptFFileByte.length;

            int dist = length/2;

            byte[] data = new byte[dist];
            System.arraycopy(encryptFFileByte,0,data,0,dist);

            String fileEncryptBase64 = Base64.encodeBase64String(data);
            EpointUploadBaohanFileReqDTO reqDTO = new EpointUploadBaohanFileReqDTO();
            reqDTO.setApplyno(bo.getApplyno());
            reqDTO.setBaohanno(bo.getBaohanno());
            reqDTO.setBaohanfile(fileEncryptBase64);
            reqDTO.setIndex("1");

            EpointBaseRespDTO resp = this.doRequest(centerNoEnum, reqDTO);

            if (!EpointRespCodeEnum.isSuccess(resp.getCode())) {
                innerBaseResp = InnerBaseResp.error(resp.getMessage());
                return  innerBaseResp;
            }

            int data2Length = length - dist;
            byte[] data2 = new byte[data2Length];
            System.arraycopy(encryptFFileByte,dist,data2,0,data2Length);

            fileEncryptBase64 = Base64.encodeBase64String(data2);
            reqDTO = new EpointUploadBaohanFileReqDTO();
            reqDTO.setApplyno(bo.getApplyno());
            reqDTO.setBaohanno(bo.getBaohanno());
            reqDTO.setBaohanfile(fileEncryptBase64);
            reqDTO.setIndex("2");

            resp = this.doRequest(centerNoEnum, reqDTO);

            if (EpointRespCodeEnum.isSuccess(resp.getCode())) {
                //同步上送完成通知
                byte[] sm4KeyByteB64 = Base64.encodeBase64(sm4KeyByte);
                byte[] sm4IVByteB64 = Base64.encodeBase64(sm4IVByte);
                String key = sm2.encryptBase64(sm4KeyByteB64, KeyType.PublicKey);
                String iv = sm2.encryptBase64(sm4IVByteB64, KeyType.PublicKey);
                String fileMd5 = MD5.create().digestHex(guaranteePath.toFile());
                innerBaseResp = this.baohanFileCompleteNotice(acceptOrderNo, centerNoEnum, reqDTO, key, iv, fileMd5);
                acceptOrderInfoService.update(AcceptOrderInfoDO.builder()
                                .uploadKey(key)
                                .uploadIv(iv)
                                .build(),
                        AcceptOrderInfoDOCondition.builder().andAcceptOrderNoEq(acceptOrderNo).build()
                );
            } else {
                innerBaseResp = InnerBaseResp.error(resp.getMessage());
            }
        } catch (Exception e) {
            log.error("保函文件上传 >> {}: [{}] 异常:{}", acceptOrderNo, centerNoEnum, e.getMessage(), e);
            innerBaseResp = InnerBaseResp.error("保函文件上传异常:" + e.getMessage());
        }

        return innerBaseResp;
    }

    private InnerBaseResp<String> baohanFileCompleteNotice(String acceptOrderNo, CenterNoEnum centerNoEnum, EpointUploadBaohanFileReqDTO uploadBaohanFileReqDTO, String key, String iv, String fileMd5) {
        EpointBaohanFileCompleteNoticeReqDTO reqDTO = new EpointBaohanFileCompleteNoticeReqDTO();
        reqDTO.setApplyno(uploadBaohanFileReqDTO.getApplyno());
        reqDTO.setBaohanno(uploadBaohanFileReqDTO.getBaohanno());
//        reqDTO.setKey(key);
//        reqDTO.setIv(iv);
        reqDTO.setBaohanfilemd5(fileMd5);

        return this.baohanFileCompleteNoticeChannelService.baohanFileCompleteNotice(acceptOrderNo, centerNoEnum, reqDTO);
    }

}
